package com.hippomeetsskunk.savanna.model;

import java.util.Random;

/**
 * TODO look at parametrisation
 * @author srzchx
 * @see Weibull distribution (inverse)
 * 
 * - quality decays over time and reaches 0 at the life time
 * - "age" of an object can be improved by doing work on it (improving the quality)
 * - effectiveness of this work depends on the real-age of the object
 * - object has a chance of breaking (inverse proportional to quality)
 */
public class DecayModel {

	private static final double MAX_QUALITY = 0.995;
	private static final double MAX_OMEGA = 5.5;
	private static final double MIN_OMEGA = 3.5;
	private static final double MAX_RESCALE = 0.5;
	private static final double MIN_RESCALE = 0.2;
	private static final double MIN_LIFETIME = 10.0;   // 10 days
	private static final double MAX_LIFETIME = 3600.0; // 10 years
	
	private Random random = new Random();
	
	private double weibullCdf(double beta, double omega, double x){
		return 1.0 - Math.exp(-beta*Math.pow(x, omega));
	}

	private double weibullQuantile(double beta, double omega, double p){
		return Math.pow(beta, -omega) * Math.pow(-Math.log1p(-p), 1.0 / omega);		
	}
	
	private double getQuality(double beta, double omega, double rescale, double lifeTime, double time){
		final double t = 1.0 - time/lifeTime;
		if (t >= MAX_QUALITY) return 1.0;
		if (t <= 0.0) return 0.0;
		
		final double q = weibullQuantile(beta, omega, t);
		final double maxQ = weibullQuantile(beta, omega, MAX_QUALITY);
		return Math.max(0, (q / maxQ - rescale) / (1.0-rescale));
	}
	
	private double getEffectiveAge(double beta, double omega, double rescale, double lifeTime, double quality){
		final double maxQ = weibullQuantile(beta, omega, MAX_QUALITY);
		double q = (quality * (1.0 - rescale) + rescale) * maxQ;
		final double t = weibullCdf(beta, omega, q);
		return lifeTime * (1.0 - t);
	}

	/**
	 * 
	 * @param lifeTime
	 * @param quality
	 * @return effective age
	 */
	public double getEffectiveAge(double lifeTime, double quality){
		final double beta = 1;
		final double omega = getOmega(lifeTime);
		final double rescale = getRescale(lifeTime);
		
		final double effectiveAge = getEffectiveAge(beta, omega, rescale, lifeTime, quality);
		return effectiveAge;
	}
	
	/**
	 * 
	 * @param lifeTime expected life time of object
	 * @param fragility chance of breaking (1 - quality) * fragility. Fragility of 0 is unbreakable.
	 * @param quality current quality level (effective age of object)
	 * @param elapsedTime elapsed time since last quality calculation
	 * @return quality at current effective age
	 */
	public double calculateQualityDecay(double lifeTime, double fragility, double quality, double elapsedTime){
		
		final double beta = 1;
		final double omega = getOmega(lifeTime);
		final double rescale = getRescale(lifeTime);
		
		final double effectiveAge = getEffectiveAge(beta, omega, rescale, lifeTime, quality);
		double q = getQuality(beta, omega, rescale, lifeTime, effectiveAge + elapsedTime);
		if (isBreaking(q, lifeTime, fragility)){
			q = 0.0;
		}
		return q;
	}

	/**
	 * fragility of 50% means 25% breaking probability for 50% quality
	 * fragility of 0% means it only breaks if quality reaches 0%.
	 * @param quality
	 * @param fragility
	 * @return broken?
	 */
	private boolean isBreaking(double quality, double lifeTime, double fragility){
		if (quality <= 0.0) return true;
		final double chance = (1.0 - quality) * fragility / lifeTime;
		final double r = random.nextDouble();
		return (r<chance);
	}
	
	private double getOmega(double lifeTime) {

		if (lifeTime < MIN_LIFETIME) return MAX_OMEGA;
		if (lifeTime > MAX_LIFETIME) return MIN_OMEGA;
		
		return (MIN_OMEGA - MAX_OMEGA) / (MAX_LIFETIME - MIN_LIFETIME) * (lifeTime - MIN_LIFETIME) + MAX_OMEGA;
	}
	
	private double getRescale(double lifeTime){
		if (lifeTime < MIN_LIFETIME) return MAX_RESCALE;
		if (lifeTime > MAX_LIFETIME) return MIN_RESCALE;
		return (MIN_RESCALE - MAX_RESCALE) / (MAX_LIFETIME - MIN_LIFETIME) * (lifeTime - MIN_LIFETIME) + MAX_RESCALE;
	}
}
